In [1]:
import sys
import datetime
print('Python:', sys.version)
import pandas as pd
print('Pandas:', pd.__version__)
import pandas_datareader as pdr
print('Pandas datareader:', pdr.__version__)
Pro jednoduchost výpočtu patří exponenciální vážený klouzavý průměr (EWMA) k hojně využívaným nástrojům pro analýzu dat, machine learning, atd. Narozdíl od jiných typů klouzavých průměrů (Simple, Exponential Moving Average, atd.) s větší periodou než 2, nepotřebuje EWMA k výpočtu aktuální hodnoty znát historii předchozích hodnot. Stačí k tomu pouze fixní váha w
(ta určuje jak velká historie se bere v potaz), hodnota aktuálního prvku a předchozí vypočítaná hodnota EWMA.
Hodnota EWMA pro každý prvek EWMA se vypočítá podle vzorce:
$$ y_t = wy_{t-1} + (1-w)x_t $$$y_t$ je výsledek EWMA
$w$ je definovaná váha, kde $w \in (0,1)$
$y_{t-1}$ je přechozí výsledek EWMA, pokud jde o první prvek, obvykle se inicializuje hodnotou 0, nebo aktuální měřenou hodnotou
$x_t$ je hodnota aktuálního prvku
Po matematickém odvození můžeme zjistit, kolik hodnot zpětně má vliv na aktuální výsledek. Vzorec pro výpočet periody $p \approx \frac{1}{1-w}$, který uvádí Andrew Ng ve výukovém video na youtube, bohužel obsahuje nepřesnost, správně by mělo být:
$$ p \approx \frac{2}{1-w} $$$w$ je definovaná váha, kde $w \in (0,1)$
$p$ je perioda, kde $p \geq 1$
Perioda nám říká, že další prvky v hlouběji v historii mají zanedbatelný vliv na výsledek EWMA pro aktuální prvek.
Jednoduše z předchozího vzorce lze odvodit jakou váhu w
mám zvolit:
$w$ je definovaná váha, kde $w \in (0,1)$
$p$ je perioda, kde $p \geq 1$
Nejprve ale získám data:
In [2]:
import datetime
start = datetime.datetime(2018, 1, 1)
end = datetime.datetime(2019, 1, 1)
spy_data = pdr.data.DataReader('SPY', 'yahoo', start, end)
spy_data.drop(['High', 'Low', 'Open', 'Close', 'Volume'], axis=1, inplace=True) # these columns are not needed
spy_data.head(5)
Out[2]:
In [3]:
p = 20
w = 1-(2/p)
w
Out[3]:
EWMA na cenovém grafu se obvykle počítá z Close/Last ceny, vzorec bude vypadat takto:
$$ y_t = wy_{y-1} + (1-w)c_t $$$y_t$ je hodnota EWMA pro aktuální cenu
$w$ je definovaná váha, kde $w \in (0,1)$
$y_{t-1}$ je přechozí výsledek EWMA, pokud jde o první hodnotu, obvykle se inicializuje hodnotou 0, nebo aktuální měřenou hodnotou
$c_t$ je aktuální hodnota Close ceny
Následující kód slouží pouze pro demonstrativní účely použití vzorce.
In [4]:
spy_data['EWMA_mannualy'] = spy_data['Adj Close']
for i in range(spy_data.shape[0]):
if i==0:
spy_data['EWMA_mannualy'][i] = spy_data['Adj Close'][i]
continue
spy_data['EWMA_mannualy'][i] = w*spy_data['EWMA_mannualy'][i-1] + (1-w)*spy_data['Adj Close'][i]
spy_data.head()
Out[4]:
Jednoduché a optimalizované řešení lze naleznout v knihovně Pandas po použití exponenciálně vážené rolling funkce df.ewm()
. Tahle funkce obsahuje parametr alpha
, který reprezentuje tzv. smoothing factor. Pandas namísto váhy používá tento smoothing factor k výpočtu takto (zdroj: dokumentace k pandas):
$y_t$ je hodnota EWMA pro aktuální cenu
$\alpha$ smoothing factor, kde $\alpha \in (0,1)$
$y_{t-1}$ je přechozí výsledek EWMA, pokud jde o první hodnotu, obvykle se inicializuje hodnotou 0, nebo aktuální měřenou hodnotou
$C_t$ je aktuální hodnota Close ceny
Po odvození lze jednoduše vypočítat smoothing factor z váhy:
$$ \alpha = 1-w $$$w$ je váha, kde $w \in (0,1)$
$\alpha$ smoothing factor, kde $\alpha \in (0,1)$
Nebo smoothing factor z periody:
$$ \alpha \approx \frac{2}{p} $$$p$ je perioda, kde $p \geq 1$
$\alpha$ smoothing factor, kde $\alpha \in (0,1)$
Pozn.: zde se neshodnu s pandas, který periodu označuje jako span
parametr a v dokumentaci figuruje vzorec $\alpha = \frac{2}{p+1}$. To by mi pak ale nekorespondovalo s Andrew Ng.
In [5]:
a = 1-w
a
Out[5]:
Případná nepřesnost může být způsobena přesností čísel s desetinnou čárkou - více zde: https://en.wikipedia.org/wiki/Floating-point_arithmetic#Accuracy_problems
In [6]:
spy_data['EWMA'] = spy_data['Adj Close'].ewm(alpha=0.1, adjust=False).mean()
spy_data['EWMA_period'] = spy_data['Adj Close'].ewm(span=p, adjust=False).mean()
spy_data.head()
Out[6]:
Pro názornost rozdílu mezi jednoduchým klouzavým průměrem (Simple Moving Average) a EWMA uvedu příklad:
In [7]:
spy_data['SMA'] = spy_data['Adj Close'].rolling(p).mean()
A nakonec si průmery zobrazím v grafu:
In [8]:
spy_data[['Adj Close', 'EWMA_mannualy', 'EWMA', 'EWMA_period', 'SMA']].plot(figsize=(16,10));
Je jednoznačně vidět, že všechny výpočty EWMA se téměř překrývají. Výpočty by měly být správné. SMA je zde jako příklad, že EWMA se dokáže rychleji přizpůsobovat posledním hodnotám.
Klouzavé průměry se používají ke zjištění průměrné hodnoty za nejbližší poslední dobu, nebo k vyhlazení roztroušených dat do jedné průměrné hodnoty. Doba je dána periodou (např. 30 dní zpět). Exponenciálně vážené klouzavé průměry (EWMA) dávají vyšší váhu bližší historii, pro čím dál starší data klesá důležitost exponenciálně.
Rozdíl mezi SMA (jednoduchý klouzavý průměr) a EWMA pro stejnou periodu je, že EWMA se rychleji přizpůsobuje posledním změnám. Pokud je EWMA v této jednoduché formě, SMA může být výpočetně náročenější. Např. SMA s periodou 20 musí získat data 20 posledních period, ty sečíst a nakonec vydělit 20. U EWMA stačí vědět předchozí hodnota EWMA vynásobit určitou váhou a přičíst aktuální měřenou hodnotu vynásobenou o mnohem větší váhu.
EWMA lze využít i u dalších jiných odvětví, nejen ve financích. EWMA pro jednoduchost výpočtu může být použito k agregaci velkého množství dat třeba v Data miningu nebo umělé inteligenci. Díky klouzavým průměrům lze zjistit jak jsou hodnoty vzdálené od svého průměru, popřípadě jaké má průměr tendence a zjišťovat trend - klesá, stoupá.
Mně osobně se líbí jednoduchost a nenáročnost výpočtu EWMA a schopnost EWMA se rychleji adaptovat na aktuální trendy.